﻿namespace EasyWeb.Data.EntityFramework
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.Composition;
    using System.Data;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Transactions;
    using EasyWeb.Data.EntityFramework.Models;
    using EasyWeb.Extensions;

    [Export(typeof(IEWEntityDefinitionDao))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class EWEntityDefinitionDao : EWDao, IEWEntityDefinitionDao
    {
        [Import(typeof(IEWDdlSqlGenerator))]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public IEWDdlSqlGenerator ddlSqlGenerator;

        public EWEntityDefinitionDao()
        {
            EWExtensionManager.CompositionContainer.SatisfyImportsOnce(this);
        }

        public virtual async Task<IEnumerable<EWEntityDefinition>> SelectAsync()
        {
            using (var transaction = EWHelpers.Data.BeginTransaction())
            {
                var result = await SelectCoreAsync<EWEntityDefinition, EWEntityDefinitionModel>();

                transaction.Complete();

                return result;
            }
        }

        public virtual async Task CreateAsync(EWEntityDefinition domain)
        {
            EWGuard.IsNotNull(domain, "domain");

            using (var transaction = EWHelpers.Data.BeginTransaction())
            {
                await CreateCoreAsync<EWEntityDefinition, EWEntityDefinitionModel>(domain);
                await CreateTableAsync(domain);

                transaction.Complete();
            }
        }

        public virtual async Task UpdateAsync(EWEntityDefinition domain)
        {
            EWGuard.IsNotNull(domain, "domain");

            using (var transaction = EWHelpers.Data.BeginTransaction())
            {
                var oldDomain = await SelectEntityAsync(domain.Id);

                await UpdateCoreAsync<EWEntityDefinition, EWEntityDefinitionModel>(domain);
                await UpdateTableAsync(oldDomain, domain);

                transaction.Complete();
            }
        }

        public virtual async Task DeleteAsync(Guid id, byte[] timestamp)
        {
            EWGuard.IsNotNull(timestamp, "timestamp");

            using (var transaction = EWHelpers.Data.BeginTransaction())
            {
                var domain = await SelectEntityAsync(id);

                await DeleteCoreAsync<EWEntityDefinitionModel>(id, timestamp);
                await DeleteTableAsync(domain);

                transaction.Complete();
            }
        }

        internal async Task CreateTableAsync(EWEntityDefinition entity)
        {
            await Execute(async db =>
            {
                var tableName = await EWCommon.GetDatabaseUserTableNameAsync(entity.InternalName);
                var columns = new List<Tuple<string, DbType, bool>>();
                foreach (var field in EWCommon.DatabaseUserTableBuiltInFields)
                {
                    columns.Add(new Tuple<string, DbType, bool>(field.Item1, field.Item2, false));
                }

                var sql = this.ddlSqlGenerator.CreateTable(tableName, columns);
                await db.Database.ExecuteSqlCommandAsync(sql);

                sql = this.ddlSqlGenerator.CreatePrimaryKey(tableName, EWCommon.DatabaseUserTablePrimaryKeyField.Item1);
                await db.Database.ExecuteSqlCommandAsync(sql);

                return default(object);
            });
        }

        internal async Task UpdateTableAsync(EWEntityDefinition oldEntity, EWEntityDefinition newEntity)
        {
            await Execute(async db =>
            {
                if (StringComparer.OrdinalIgnoreCase.Compare(oldEntity.InternalName, newEntity.InternalName) != 0)
                {
                    var oldTableName = await EWCommon.GetDatabaseUserTableNameAsync(oldEntity.InternalName);
                    var newTableName = await EWCommon.GetDatabaseUserTableNameAsync(newEntity.InternalName);
                    var sql = this.ddlSqlGenerator.ChangeTableName(oldTableName, newTableName);
                    await db.Database.ExecuteSqlCommandAsync(sql);
                }

                return default(object);
            });
        }

        internal async Task DeleteTableAsync(EWEntityDefinition entity)
        {
            await Execute(async db =>
            {
                var tableName = await EWCommon.GetDatabaseUserTableNameAsync(entity.InternalName);
                var sql = this.ddlSqlGenerator.DeleteTable(tableName);
                await db.Database.ExecuteSqlCommandAsync(sql);

                return default(object);
            });
        }

        internal async Task<EWEntityDefinition> SelectEntityAsync(Guid id)
        {
            var result = (await SelectAsync()).FirstOrDefault(d => d.Id == id);
            if (result == null)
            {
                throw new EWDataConcurrencyException(string.Format("The entity with ID '{0}' does not exist.", id));
            }

            return result;
        }
    }
}
